package pcloud.server;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.List;

import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.ftplet.FtpFile;
import org.apache.ftpserver.ftplet.FtpReply;
import org.apache.ftpserver.ftplet.FtpRequest;
import org.apache.ftpserver.ftplet.FtpSession;
import org.apache.ftpserver.ftplet.FtpletResult;

import com.sun.xml.internal.ws.util.ByteArrayBuffer;

import pcloud.server.datastore.ClientDataStore;

public class PCloudFtplet extends org.apache.ftpserver.ftplet.DefaultFtplet

{
	@Override
	public FtpletResult onConnect(FtpSession session) throws FtpException,
			IOException
	{
		String logText = "ID: " + session.getSessionId() + "\r\n"
				+ "ClientAddress: " + session.getClientAddress() + "\r\n"
				+ "User: " + session.getUser() + "\r\n" + "ConnectionTime: "
				+ session.getConnectionTime();
		Logger.log(logText, false);
		return null;
	}

	@Override
	public FtpletResult beforeCommand(FtpSession session, FtpRequest request)
			throws FtpException, IOException
	{
		String command = request.getCommand();
		String argument = request.getArgument();
		argument = argument == null ? "" : argument;

		String logText = "Client:'" + session.getAttribute("CLIENT_ID")
				+ "'; Command:'" + command + "'; Argument:'" + argument + "'";
		Logger.log(logText, true);

		if ("CLIENT_ID".equals(command) && request.hasArgument()) {
			onIdentifyClient(session, request);
		}

		return super.beforeCommand(session, request);
	}

	@Override
	public FtpletResult afterCommand(FtpSession session, FtpRequest request,
			FtpReply reply) throws FtpException, IOException
	{
		String command = request.getCommand().toUpperCase();
		String argument = request.getArgument();

		int replyCode = reply.getCode();
		Logger.log("After command, reply code: " + replyCode);
		if (replyCode > 300) {
			Logger.log("command failed (reply code: "+replyCode+"), skip after command handler", true);
			return super.afterCommand(session, request, reply);
		}

		Object attrClientId = session.getAttribute("CLIENT_ID");
		if (attrClientId==null) {
			Logger.log("No client id, skip after command handler", true);
			return super.afterCommand(session, request, reply);
		}
		String clientId = attrClientId.toString();

		if (argument != null && argument.startsWith("/datastore/")) {
			Logger.log("a client get tasks from datastore! ", true);
			return super.afterCommand(session, request, reply);
		}

		if (!("DELE".equals(command) || "STOR".equals(command)
				|| "RETR".equals(command) || "RMD".equals(command)
				|| "MKD".equals(command) || "APPE".equals(command)
				|| "STOU".equals(command) || "RNTO".equals(command))) {
			return super.afterCommand(session, request, reply);
		}

		Logger.log("After command handler start! ", true);
		try {
			String workingDirectory = session.getFileSystemView()
					.getWorkingDirectory().getAbsolutePath();
			String requestFilePath = request.getArgument();
			String targetPath = requestFilePath.indexOf("/") >= 0 ? requestFilePath
					: workingDirectory + "/" + requestFilePath;
	
			String homeDirectory = session.getUser().getHomeDirectory();
			ClientDataStore dataStore = new ClientDataStore(clientId, homeDirectory);

			Logger.log("updateTask( " + targetPath + ", " + command + ")", true);
			dataStore.updateTask(targetPath, command);
		} catch (Exception e) {
			ByteArrayOutputStream bytesStream = new ByteArrayOutputStream();
			PrintStream s = new PrintStream(bytesStream);
			e.printStackTrace(s);
			
			Logger.log("handle failed : "+bytesStream.toString(), true);
		}

		return super.afterCommand(session, request, reply);
	}

	private void onIdentifyClient(FtpSession session, FtpRequest request)
	{
		String clientId = request.getArgument();
		Logger.log("CLIENT_ID: " + clientId + "\r\n", true);
		session.setAttribute("CLIENT_ID", clientId);
		String homeDirectory = session.getUser().getHomeDirectory();
		ClientDataStore dataStore = new ClientDataStore(clientId, homeDirectory);
		if (!dataStore.hasClient(clientId)) {
			dataStore.createClient(clientId);
		}
	}

	/*--由于FtpFile封装的限制, 无法取到父目录, 在Ftplet中很难实现创建多级目录, 
	 * 因此去掉这段代码, 改为直接修改Apache FTP Server源码中的MKD类, 
	 * 把其中mkdir()函数调用改为mkdirs()调用
	// @Override
	// public FtpletResult onMkdirStart(FtpSession session, FtpRequest request)
	// throws FtpException, IOException
	// {
	// // 如果没有client id, 按照正常命令处理
	// Object clientIDProperty = session.getAttribute("CLIENT_ID");
	// if (clientIDProperty == null) {
	// return super.onMkdirStart(session, request);
	// }
	//
	// // 如果有client id, 检查待创建目录的所有父目录, 如果没有, 则帮助创建
	// // 一旦出现错误,就可以忽略其他FTPLets了, 但还需要正常的MKD命令来写入返回信息, 即返回FtpletResult.NO_FTPLET
	// // argument check
	// String fileName = request.getArgument();
	// if (fileName == null) {
	// return FtpletResult.NO_FTPLET;
	// }
	//
	// // get file object
	// FtpFile file = null;
	// try {
	// file = session.getFileSystemView().getFile(fileName);
	// } catch (Exception ex) {
	// Logger.log("Exception getting file object: "+ex.getMessage());
	// }
	// if (file == null) {
	// return FtpletResult.NO_FTPLET;
	// }
	//
	// // check permission
	// fileName = file.getAbsolutePath();
	// if (!file.isWritable()) {
	// return FtpletResult.NO_FTPLET;
	// }
	//
	// // check file existance
	// if (file.doesExist()) {
	// return FtpletResult.NO_FTPLET;
	// }
	//
	// // now create directory
	// if (createAllAncestor(file)) {
	//
	// // write log message
	// String userName = session.getUser().getName();
	// Logger.log("Directory create : " + userName + " - " + fileName);
	// }
	//
	// return super.onRmdirStart(session, request);
	// }

	// private boolean createAllAncestor(FtpFile file)
	// {
	// String fullPath = file.getAbsolutePath();
	// String parentPath = fullPath.substring(0, fullPath.lastIndexOf('/'));
	// parentFile = new NativeFtpFile(parentPath);
	// return false;
	// }
	 */

	@Override
	public FtpletResult onRmdirStart(FtpSession session, FtpRequest request)
			throws FtpException, IOException
	{
		// 如果没有client id, 按照正常命令处理
		Object clientIDProperty = session.getAttribute("CLIENT_ID");
		if (clientIDProperty == null) {
			return super.onRmdirStart(session, request);
		}
		
		// 如果有client id, 首先做可删除的检查, 然后尝试递归删除目录下所有内容
		// 为了不产生误删除, 必须先做好所有的检查, 也就是把RMD Command当中的检查都做一遍, 因此,
		// 一旦出现错误,就可以忽略其他FTPLets了, 但还需要正常的RMD命令来写入返回信息,
		// 即返回FtpletResult.NO_FTPLET

		// 在RMD Command中,有这句 reset state variables, 但FtpSession与FtpIoSession不同,
		// 没有这个方法
		// session.resetState();

		String fileName = request.getArgument();
		if (fileName == null) {
			return FtpletResult.NO_FTPLET;
		}

		FtpFile file = null;
		try {
			file = session.getFileSystemView().getFile(fileName);
		} catch (Exception ex) {
			Logger.log("Exception getting file object");
		}
		if (file == null) {
			return FtpletResult.NO_FTPLET;
		}

		fileName = file.getAbsolutePath();

		// first let's make sure the path is a directory
		if (!file.isDirectory()) {
			return FtpletResult.NO_FTPLET;
		}

		// then make sure that the client did not request the deletion of
		// current working directory.
		FtpFile cwd = session.getFileSystemView().getWorkingDirectory();
		if (file.equals(cwd)) {
			return FtpletResult.NO_FTPLET;
		}

		// now check to see if the user have permission to delete this directory
		if (!file.isRemovable()) {
			return FtpletResult.NO_FTPLET;
		}

		// now delete directory
		if (removeAllDescendant(file)) {
			Logger.log("Directory's all descendants are removed : '"
					+ file.getAbsolutePath() + "'");
		} else {
			Logger.log("Directory's one descendants can not be removed : '"
					+ file.getAbsolutePath() + "'");
			return FtpletResult.NO_FTPLET;
		}

		return super.onRmdirStart(session, request);
	}

	private boolean removeAllDescendant(FtpFile file)
	{
		List<FtpFile> childFiles = file.listFiles();
		// 如果没有子文件或子目录, 直接返回成功
		if (childFiles == null) {
			return true;
		}
		// 递归删除所有子文件和目录
		for (FtpFile childFile : childFiles) {
			if (childFile.isFile()) {
				boolean ret = childFile.delete();
				// 如果有一个子文件无法删除, 就认为无法本目录无法删除
				if (!ret) {
					return false;
				}
			} else {
				boolean ret = traverseRemove(childFile);
				// 如果有一个子目录无法删除, 就认为无法本目录无法删除
				if (!ret) {
					return false;
				}
			}
		}
		return true;
	}

	private boolean traverseRemove(FtpFile file)
	{
		if (file == null) {
			return true;
		}

		List<FtpFile> childFiles = file.listFiles();
		if (childFiles == null) {
			return file.delete();
		}

		for (FtpFile childFile : childFiles) {
			if (childFile.isFile()) {
				boolean ret = childFile.delete();
				// 如果有一个子文件无法删除, 就认为无法本目录无法删除
				if (!ret) {
					return false;
				}
			} else {
				boolean ret = traverseRemove(childFile);
				// 如果有一个子目录无法删除, 就认为无法本目录无法删除
				if (!ret) {
					return false;
				}
			}
		}

		return file.delete();
	}

}
